﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using TTApp.Phone.Silverlight.Hexagons;

namespace ZoomExperiment
{
    public partial class ThirdTest : PhoneApplicationPage
    {
        #region //----------------------------- Class Private Fields --------------------------------------//
        /// <summary>
        /// Hình chữ nhật chứa kích thước nhỏ nhất của image
        /// </summary>
        private Rect _innerBound;

        /// <summary>
        /// Kích thước của image nhìn thấy, tức là phần nằm trong viewbox
        /// </summary>
        private Size _imageSize;
        /// <summary>
        /// Tỉ lệ của _imageSize so với kích thước vật lý của nó
        /// </summary>
        private Point _imageSizeRate;

        /// <summary>
        /// Độ scale của image khi bắt đầu pinch
        /// </summary>
        private double _initialScale;
        /// <summary>
        /// Tỉ lệ scale nhỏ nhất có thể của image. Được tính dựa vào _innerBound
        /// </summary>
        private double _minScale;
        /// <summary>
        /// Tỉ lệ scale lớn nhất
        /// </summary>
        private double _maxScale;

        /// <summary>
        /// Tỉ lệ scale thích hợp nhỏ nhất. Nếu image đang ở tỉ lệ nhỏ hơn tỉ lệ này, nó sẽ
        /// tự động được đưa về tỉ lệ này
        /// </summary>
        private double _preferredMinScale;
        /// <summary>
        /// Tương tự như _preferredMinScale
        /// </summary>
        private double _preferredMaxScale;

        /// <summary>
        /// Tâm 2 ngón tay trong 1 lần PinchDelta
        /// </summary>
        private Point _pinchCenter;

        /// <summary>
        /// Tỉ lệ để tính _innerBound
        /// </summary>
        private double _boundRate = 0.3;
        /// <summary>
        /// Thay đổi độ lớn của vận tốc vuốt
        /// </summary>
        private double _flickVelocityScale = 0.45;

        /// <summary>
        /// Được bật nếu quá trình drag bắt đầu khi không có storyboard nào đang chạy
        /// </summary>
        private bool _isDragEnabled;
        /// <summary>
        /// Được bật nếu quá trình pinch bắt đầu khi không có storyboard nào đang chạy
        /// </summary>
        private bool _isPinchEnable;

        /// <summary>
        /// Trạng thái của restoreTranslateStoryboard
        /// </summary>
        StoryboardState _restoreTranslateStoryboardState = StoryboardState.Unset;
        /// <summary>
        /// Trạng thái của restoreScaleStoryboardState
        /// </summary>
        StoryboardState _restoreScaleStoryboardState = StoryboardState.Unset;
        /// <summary>
        /// Trạng thái của flickStoryboard
        /// </summary>
        StoryboardState _flickStoryboardState = StoryboardState.Unset;

        /// <summary>
        /// Thời gian chạy mặc định cho mọi storyboard.
        /// </summary>
        private Duration _defaultDuration = new Duration(TimeSpan.FromSeconds(0.45));
        /// <summary>
        /// Thời gian cho trôi của image sau khi được flick
        /// </summary>
        private Duration _flickDuration = new Duration(TimeSpan.FromSeconds(0.75));

        // Test only
        private Stretch[] _stretch = { Stretch.Fill, Stretch.Uniform, Stretch.UniformToFill };
        #endregion

        #region //----------------------------- Class Public Constructors ---------------------------------//
        public ThirdTest()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(OnLoaded);
            SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }
        #endregion

        #region //----------------------------- Class CLR Properties --------------------------------------//
        /// <summary>
        /// Lấy giá trị nhỏ nhất có thể scale được. Giá trị này được tính dựa theo kích thước của 
        /// image, viewbox và root. Không gán được.
        /// </summary>
        public double MinScale
        {
            get { return _minScale; }
        }

        /// <summary>
        /// Lấy hoặc gán giá trị scale lớn nhất có thể.
        /// </summary>
        public double MaxScale
        {
            get
            {
                return _maxScale;
            }
            set
            {
                if (value < _preferredMaxScale)
                {
                    throw new ArgumentOutOfRangeException("Value must be greater than or equal to PreferredMaxScale");
                }
                _maxScale = value;
            }
        }

        /// <summary>
        /// Độ scale thích hợp mức nhỏ. Nếu image đang ở mức nhỏ hơn thì tự động được đưa về mức này
        /// </summary>
        public double PrefferedMinScale
        {
            get
            {
                return _preferredMinScale;
            }
            set
            {
                if (!(_minScale <= value && value <= _preferredMaxScale))
                {
                    throw new ArgumentOutOfRangeException("Value must be between MinScale and PreferredMaxScale");
                }
                _preferredMinScale = value;
            }
        }

        /// <summary>
        /// Độ scale thích hợp mức lớn. Nếu image đang ở mức lớn hơn thì tự động được đưa về mức này
        /// </summary>
        public double PreferredMaxScale
        {
            get
            {
                return _preferredMaxScale;
            }
            set
            {
                if (!(_preferredMinScale <= value && value <= _maxScale))
                {
                    throw new ArgumentOutOfRangeException("Value must be between PreferredMinScale and MaxScale");
                }
                _preferredMaxScale = value;
            }
        }

        /// <summary>
        /// Lấy hoặc gán độ dãn của ảnh. Kiểu None không hỗ trợ.
        /// </summary>
        public Stretch Stretch
        {
            get
            {
                return viewbox.Stretch;
            }
            set
            {
                if (value == Stretch.None)
                {
                    throw new NotSupportedException("Không hỗ trợ None. Chọn Fill, Uniform hoặc UniformToFill.");
                }
                else
                {
                    viewbox.Stretch = value;
                }
            }

        }
        #endregion

        #region //----------------------------- Private Event Handlers ------------------------------------//
        private void OnDoubleTap(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {
            _pinchCenter = args.GetPosition(image);
            _pinchCenter.X *= transform.ScaleX;
            _pinchCenter.Y *= transform.ScaleY;

            // Sai số
            if (Math.Abs(transform.ScaleX - _preferredMaxScale) < 0.1)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMinScale);
            }
            else
            {
                SetBackEaseAmplitude(1.0);
                RestoreScale(_preferredMaxScale);
            }
        }

        private void OnDragStarted(object sender, DragStartedGestureEventArgs args)
        {
            if (IsGestureEnable())
            {
                _isDragEnabled = true;
            }
        }

        private void OnDragDelta(object sender, DragDeltaGestureEventArgs args)
        {
            if (_isDragEnabled)
            {
                Point transitionDelta = new Point(
                args.HorizontalChange, args.VerticalChange);

                if (IsDragValid(transitionDelta))
                {
                    UpdateImagePosition(transitionDelta);
                }
            }
        }

        private void OnDragCompleted(object sender, DragCompletedGestureEventArgs args)
        {
            _isDragEnabled = false;

            // Không có flick thì kiểm tra vị trí
            if (args.HorizontalVelocity == 0 && args.VerticalVelocity == 0)
            {
                CheckPosition();
            }
        }

        private void OnFlick(object sender, FlickGestureEventArgs args)
        {
            if (IsGestureEnable() && IsFlickValid(new Point(args.HorizontalVelocity, args.VerticalVelocity)))
            {
                // Lượng dịch chuyển theo các trục
                double flickXTranslate = args.HorizontalVelocity * _flickVelocityScale * _flickDuration.TimeSpan.TotalSeconds;
                double flickYTranslate = args.VerticalVelocity * _flickVelocityScale * _flickDuration.TimeSpan.TotalSeconds;

                UpdateFlick(new Point(flickXTranslate, flickYTranslate));
            }
        }

        private void OnGestureBegin(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {

        }

        private void OnGestureCompleted(object sender, Microsoft.Phone.Controls.GestureEventArgs args)
        {
            // Nếu không có storyboard nào đang chạy thì kiểm tra vị trí
            if (_restoreScaleStoryboardState != StoryboardState.Running &&
                _restoreTranslateStoryboardState != StoryboardState.Running &&
                _flickStoryboardState != StoryboardState.Running)
            {
                CheckPosition();
            }
        }

        private void OnPinchStarted(object sender, PinchStartedGestureEventArgs args)
        {
            if (IsGestureEnable())
            {
                _isPinchEnable = true;

                _initialScale = transform.ScaleX;

                Point finger1 = args.GetPosition(image, 0);
                Point finger2 = args.GetPosition(image, 1);

                _pinchCenter = new Point((finger1.X + finger2.X) / 2, (finger1.Y + finger2.Y) / 2);
                _pinchCenter.X *= transform.ScaleX;
                _pinchCenter.Y *= transform.ScaleY;
            }
        }

        private void OnPinchDelta(object sender, PinchGestureEventArgs args)
        {
            if (_isPinchEnable)
            {
                double currentScale = _initialScale * args.DistanceRatio;
                double validScale = GetValidScale(currentScale);

                Point transitionDelta = CalculateTransition(validScale);

                UpdateImageScale(validScale);
                UpdateImagePosition(transitionDelta);
            }
        }

        private void OnPinchCompleted(object sender, PinchGestureEventArgs args)
        {
            _isPinchEnable = false;

            CheckScale();
        }

        private void OnLoaded(object sender, RoutedEventArgs args)
        {
            InitializeVariables();
        }

        private void OnFlickStoryboardCompleted(object sender, EventArgs args)
        {
            _flickStoryboardState = StoryboardState.Stopped;
            CheckPosition();
        }

        private void OnRestoreScaleStoryboardCompleted(object sender, EventArgs args)
        {
            _restoreScaleStoryboardState = StoryboardState.Stopped;
        }

        private void OnRestoreTranslateStoryboardCompleted(object sender, EventArgs args)
        {
            _restoreTranslateStoryboardState = StoryboardState.Stopped;
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs args)
        {
            transform.TranslateX = transform.TranslateY = 0;
            transform.ScaleX = transform.ScaleY = 1;

            InitializeVariables();
        }
        #endregion

        #region //----------------------------- Class Private Methods -------------------------------------//
        #region //--------- Checking ----------//
        /// <summary>
        /// Kiểm tra xem có kéo được image không. 
        /// </summary>
        /// <param name="transitionDelta">Dùng để xác định hướng chuyển động, không dùng độ lớn</param>
        /// <returns>true nếu 1 trong 2 phương x, y của image có thể chuyển động được</returns>
        private bool IsDragValid(Point transitionDelta)
        {
            bool canMoveX = true, canMoveY = true;

            // Vị trí hiện tại của image
            Rect imagePosition = GetImagePosition();

            if (transitionDelta.X > 0 && imagePosition.Left >= _innerBound.Left)
            {
                canMoveX = false;
            }
            else if (transitionDelta.X < 0 && imagePosition.Right <= _innerBound.Right)
            {
                canMoveX = false;
            }

            if (transitionDelta.Y > 0 && imagePosition.Top >= _innerBound.Top)
            {
                canMoveY = false;
            }
            else if (transitionDelta.Y < 0 && imagePosition.Bottom <= _innerBound.Bottom)
            {
                canMoveY = false;
            }

            return (canMoveX || canMoveY);
        }

        /// <summary>
        /// Kiểm tra xem có thể thao tác được không.
        /// </summary>
        /// <returns>false nếu có 1 storyboard nào đó đang chạy</returns>
        private bool IsGestureEnable()
        {
            return (_restoreTranslateStoryboardState != StoryboardState.Running &&
                    _restoreScaleStoryboardState != StoryboardState.Running);
        }

        /// <summary>
        /// Kiểm tra xem có scale được image hay không
        /// </summary>
        /// <param name="scale">Giá trị scale mới</param>
        /// <returns></returns>
        private bool IsScaleValid(double scale)
        {
            if (scale < _minScale)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Kiểm tra xem có flick được hay không.
        /// </summary>
        /// <param name="velocity">Vận tốc của flick</param>
        /// <returns>false nếu không chuyển động được theo trục nào</returns>
        private bool IsFlickValid(Point velocity)
        {
            bool canFlickX = true, canFlickY = true;
            Rect imagePosition = GetImagePosition();

            // Đứng im theo trục X
            if (velocity.X == 0)
            {
                canFlickX = false;
            }
            // Sang phải theo trục X
            if (velocity.X > 0 && imagePosition.Left >= _innerBound.Left)
            {
                canFlickX = false;
            }
            // Sang trái theo trục X
            else if (velocity.X < 0 && imagePosition.Right <= _innerBound.Right)
            {
                canFlickX = false;
            }

            // Đứng im theo trục Y
            if (velocity.Y == 0)
            {
                canFlickY = false;
            }
            else if (velocity.Y > 0 && imagePosition.Top >= _innerBound.Top)
            {
                canFlickY = false;
            }
            else if (velocity.Y < 0 && imagePosition.Bottom <= _innerBound.Bottom)
            {
                canFlickY = false;
            }

            return (canFlickX || canFlickY);
        }
        #endregion

        #region //--------- Position ----------//
        /// <summary>
        /// Cập nhật vị trí của image.
        /// </summary>
        /// <param name="transitionDelta">Lượng dịch chuyển có thể sẽ áp dụng</param>
        private void UpdateImagePosition(Point transitionDelta)
        {
            // Vị trí của ảnh trước khi dịch
            Rect imagePosition = new Rect(
                transform.TranslateX / _imageSizeRate.X,
                transform.TranslateY / _imageSizeRate.Y,
                _imageSize.Width * transform.ScaleX,
                _imageSize.Height * transform.ScaleY);

            // Vị trí mới của ảnh sau khi được dịch
            Rect newPosition = new Rect(
                imagePosition.Left + transitionDelta.X,
                imagePosition.Top + transitionDelta.Y,
                imagePosition.Width,
                imagePosition.Height);

            // Biến lưu giá trị dịch cuối cùng
            Point newTransition = new Point(transitionDelta.X, transitionDelta.Y);

            if (newPosition.Left >= _innerBound.Left)
            {
                newTransition.X = _innerBound.Left - imagePosition.Left;
            }
            else if (newPosition.Right <= _innerBound.Right)
            {
                newTransition.X = _innerBound.Right - imagePosition.Right;
            }

            if (newPosition.Top >= _innerBound.Top)
            {
                newTransition.Y = _innerBound.Top - imagePosition.Top;
            }
            else if (newPosition.Bottom <= _innerBound.Bottom)
            {
                newTransition.Y = _innerBound.Bottom - imagePosition.Bottom;
            }

            ApplyTransition(newTransition);
        }

        /// <summary>
        /// Áp dụng dịch chuyển cho image
        /// </summary>
        /// <param name="transition">Lượng dịch chuyển so với root.</param>
        private void ApplyTransition(Point transition)
        {
            transform.TranslateX += (transition.X * _imageSizeRate.X);
            transform.TranslateY += (transition.Y * _imageSizeRate.Y);
        }

        /// <summary>
        /// Đưa ảnh trở về trạng thái thích hợp sau khi zoom, drag hoặc flick
        /// </summary>
        private void CheckPosition()
        {
            // Vị trí hiện tại của image, tính theo tọa độ của viewbox
            Rect imagePosition = new Rect(
                transform.TranslateX / _imageSizeRate.X,
                transform.TranslateY / _imageSizeRate.Y,
                _imageSize.Width * transform.ScaleX,
                _imageSize.Height * transform.ScaleY);

            RestorePosition(imagePosition);
        }

        /// <summary>
        /// Kiểm tra vị trí hiện tại của image có bị vượt góc hay không. Nếu có thì điều chỉnh
        /// </summary>
        /// <param name="imagePosition">Vị trí hiện tại của image so với viewbox</param>
        private void RestorePosition(Rect imagePosition)
        {
            // Độ dịch của ảnh nếu cần thiết
            double translateX = 0, translateY = 0;

            // Chuyển sang tọa độ so với root, sẽ vẫn giữ nguyên nếu cạnh của viewbox >= cạnh của root
            Rect imageRootPosition = GetImageRootPosition();

            // Chiều rộng của ảnh nhỏ hơn root và viewbox cũng nhỏ hơn root thì căn vào giữa viewbox
            if (imagePosition.Width < root.ActualWidth && viewbox.ActualWidth < root.ActualWidth)
            {
                double viewboxCenter = viewbox.ActualWidth / 2;
                double imageCenter = imagePosition.Left + imagePosition.Width / 2;

                translateX = (viewboxCenter - imageCenter) * _imageSizeRate.X;
            }
            // Còn không thì căn so với root, dùng tọa độ so với root
            else
            {
                if (imageRootPosition.Left >= 0)
                {
                    translateX = -(imageRootPosition.Left * _imageSizeRate.X);
                }
                else if (imageRootPosition.Right <= root.ActualWidth)
                {
                    translateX = (root.ActualWidth - imageRootPosition.Right) * _imageSizeRate.X;
                }
            }

            // Chiều cao ảnh nhỏ hơn root và chiều cao của viewbox cũng nhỏ hơn root thì căn vào giữa viewbox
            if (imagePosition.Height < root.ActualHeight && viewbox.ActualHeight < root.ActualHeight)
            {
                double viewboxCenter = viewbox.ActualHeight / 2;
                double imageCenter = imagePosition.Top + imagePosition.Height / 2;

                translateY = (viewboxCenter - imageCenter) * _imageSizeRate.Y;
            }
            // Còn không thì căn so với root, dùng tọa độ so với root
            else
            {
                if (imageRootPosition.Top >= 0)
                {
                    translateY = -(imageRootPosition.Top * _imageSizeRate.Y);
                }
                else if (imageRootPosition.Bottom <= root.ActualHeight)
                {
                    translateY = (root.ActualHeight - imageRootPosition.Bottom) * _imageSizeRate.Y;
                }
            }

            // Dịch chuyển nếu cần thiết
            if (translateX != 0 || translateY != 0)
            {
                SetBackEaseAmplitude(0.35);
                TranslateAnimation(translateX, translateY, _defaultDuration);
            }
        }

        /// <summary>
        /// Dịch chuyển vị trí của image theo tham số
        /// </summary>
        /// <param name="byOnX">Độ dịch chuyển theo trục x</param>
        /// <param name="byOnY">Độ dịch chuyển theo trục y</param>
        /// <param name="duration">Thời gian dịch chuyển</param>
        private void TranslateAnimation(double byOnX, double byOnY, Duration duration)
        {
            if (byOnX != 0 || byOnY != 0)
            {
                restoreTranslateXAnimation.By = byOnX;
                restoreTranslateYAnimation.By = byOnY;
                restoreTranslateXAnimation.Duration = restoreTranslateYAnimation.Duration = duration;

                restoreTranslateStoryboard.Begin();
                _restoreTranslateStoryboardState = StoryboardState.Running;
            }
        }
        #endregion

        #region //--------- Scale -------------//
        /// <summary>
        /// Kiểm tra lượng scale, tính toán cho thỏa mãn rồi áp dụng
        /// </summary>
        /// <param name="newScale">Lượng scale có thể sẽ được áp dụng</param>
        private void UpdateImageScale(double newScale)
        {
            ApplyScale(newScale);
        }

        /// <summary>
        /// Áp dụng scale cho image
        /// </summary>
        /// <param name="newScale">Lượng scale mới của image</param>
        private void ApplyScale(double newScale)
        {
            transform.ScaleX = transform.ScaleY = newScale;
        }

        /// <summary>
        /// Kiểm tra độ scale hiện tại của image có nhỏ hơn _preferredMinScale hay có lớn hơn 
        /// _preferredMaxScale không. Có thì điều chỉnh.
        /// </summary>
        private void CheckScale()
        {
            double currentScale = transform.ScaleX;

            if (currentScale < _preferredMinScale)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMinScale);
            }
            else if (currentScale > _preferredMaxScale)
            {
                SetBackEaseAmplitude(0.35);
                RestoreScale(_preferredMaxScale);
            }
            else
            {
                CheckPosition();
            }
        }

        /// <summary>
        /// Điều chỉnh độ scale của image đến newScale.
        /// </summary>
        /// <param name="currentScale">Độ scale mới của image</param>
        private void RestoreScale(double newScale)
        {
            double currentScale = transform.ScaleX;
            double translateX = 0, translateY = 0;

            Point centerTranslateDelta = CalculateTransition(newScale);
            Point totalTranslateDelta = centerTranslateDelta;

            // Vị trí của ảnh sau khi scale và dịch chuyển tâm theo viewbox và kích thước theo root
            Rect newImagePosition = GetImagePosition(
                new Point(
                    transform.TranslateX + centerTranslateDelta.X * _imageSizeRate.X,
                    transform.TranslateY + centerTranslateDelta.Y * _imageSizeRate.Y),
                newScale);

            // Cạnh trái
            if (newImagePosition.Left > 0)
            {
                totalTranslateDelta.X += -newImagePosition.Left;
            }
            // Cạnh phải
            else
            {
                // viewbox nhỏ hơn root, tính theo tọa độ viewbox
                if (viewbox.ActualWidth <= root.ActualWidth && newImagePosition.Right < viewbox.ActualWidth)
                {
                    totalTranslateDelta.X += (viewbox.ActualWidth - newImagePosition.Right);
                }
                // không thì tính theo tọa độ root
                else if (viewbox.ActualWidth > root.ActualWidth && newImagePosition.Right < root.ActualWidth)
                {
                    totalTranslateDelta.X += (root.ActualWidth - newImagePosition.Right);
                }
            }

            // Cạnh trên
            if (newImagePosition.Top > 0)
            {
                totalTranslateDelta.Y += -newImagePosition.Top;
            }
            // Cạnh dưới
            else
            {
                // viewbox nhỏ hơn root, tính theo tọa độ viewbox
                if (viewbox.ActualHeight <= root.ActualHeight && newImagePosition.Bottom < viewbox.ActualHeight)
                {
                    totalTranslateDelta.Y += (viewbox.ActualHeight - newImagePosition.Bottom);
                }
                else if (viewbox.ActualHeight > root.ActualHeight && newImagePosition.Bottom < root.ActualHeight)
                {
                    totalTranslateDelta.Y += (root.ActualHeight - newImagePosition.Bottom);
                }
            }

            TranslateAnimation(totalTranslateDelta.X * _imageSizeRate.X, totalTranslateDelta.Y * _imageSizeRate.Y, _defaultDuration);
            ScaleAnimation(newScale, _defaultDuration);
        }

        /// <summary>
        /// Phóng image theo theo số
        /// </summary>
        /// <param name="newScale">Độ scale mới của image</param>
        /// <param name="duration">Thời gian phóng</param>
        private void ScaleAnimation(double newScale, Duration duration)
        {
            if (newScale != transform.ScaleX)
            {
                restoreScaleXAnimation.To = restoreScaleYAnimation.To = newScale;
                restoreScaleXAnimation.Duration = restoreScaleYAnimation.Duration = duration;

                restoreScaleStoryboard.Begin();
                _restoreScaleStoryboardState = StoryboardState.Running;
            }
        }
        #endregion

        #region //--------- Flick -------------//
        /// <summary>
        /// Tính toán lượng khoảng cách image sẽ trôi
        /// </summary>
        /// <param name="transitionDelta">Khoảng cách có thể trôi tính theo root</param>
        private void UpdateFlick(Point transitionDelta)
        {
            // Tọa độ ảnh tính theo viewbox và kích thước tính theo root
            Rect imagePosition = GetImagePosition();

            // Tọa độ ảnh sau khi flick (nếu flick thỏa mãn)
            Rect newImagePosition = GetImagePosition(
                new Point(
                    transform.TranslateX + transitionDelta.X * _imageSizeRate.X,
                    transform.TranslateY + transitionDelta.Y * _imageSizeRate.Y),
                transform.ScaleX);

            // Lượng khoảng cách thực sự mà image sẽ di chuyển
            double realXTranslate = transitionDelta.X;
            double realYTranslate = transitionDelta.Y;

            // Trục X
            if (newImagePosition.Left > _innerBound.Left)
            {
                realXTranslate -= (newImagePosition.Left - _innerBound.Left);
            }
            else if (newImagePosition.Right < _innerBound.Right)
            {
                realXTranslate += (_innerBound.Right - newImagePosition.Right);
            }

            // Trục Y
            if (newImagePosition.Top > _innerBound.Top)
            {
                realYTranslate -= (newImagePosition.Top - _innerBound.Top);
            }
            else if (newImagePosition.Bottom < _innerBound.Bottom)
            {
                realYTranslate += (_innerBound.Bottom - newImagePosition.Bottom);
            }

            // Tính thời gian theo khoảng cách thực sự di chuyển
            double timeOnX = (realXTranslate / transitionDelta.X) * _flickDuration.TimeSpan.TotalSeconds;
            double timeOnY = (realYTranslate / transitionDelta.Y) * _flickDuration.TimeSpan.TotalSeconds;

            double minTime = Math.Min(timeOnX, timeOnY);

            ApplyFlick(new Point(realXTranslate, realYTranslate), new Duration(TimeSpan.FromSeconds(minTime)));
        }

        /// <summary>
        /// Áp dụng flick animation cho image.
        /// </summary>
        /// <param name="transition">Lượng khoảng cách thực sự sẽ di chuyển tính theo root</param>
        /// <param name="duration">Khoảng thời gian di chuyển</param>
        private void ApplyFlick(Point transition, Duration duration)
        {
            FlickAnimation(transition.X * _imageSizeRate.X, transition.Y * _imageSizeRate.Y, duration);
        }

        /// <summary>
        /// Flick image theo tham số
        /// </summary>
        /// <param name="byOnX">Độ flick theo trục X tính theo viewbox</param>
        /// <param name="byOnY">Độ flick theo trục Y tính theo viewbox</param>
        /// <param name="duration">Khoảng thời gian diễn ra flick</param>
        private void FlickAnimation(double byOnX, double byOnY, Duration duration)
        {
            if (byOnX != 0 || byOnY != 0)
            {
                flickXAnimation.By = byOnX;
                flickYAnimation.By = byOnY;
                flickXAnimation.Duration = flickYAnimation.Duration = duration;

                flickStoryboard.Begin();
                _flickStoryboardState = StoryboardState.Running;
            }
        }
        #endregion

        #region //--------- Getter ------------//
        /// <summary>
        /// Tính độ dịch chuyển tâm khi scale
        /// </summary>
        /// <param name="newScale">Lượng scale mới</param>
        /// <returns>Lượng dịch chuyển cần để cho tâm mới trùng với tâm cũ trên màn hình</returns>
        private Point CalculateTransition(double newScale)
        {
            // Tọa độ tâm khi chưa scale
            Point originalPinchCenter = new Point(
                _pinchCenter.X / transform.ScaleX,
                _pinchCenter.Y / transform.ScaleY);

            // Tọa độ tâm cũ khi áp dụng scale mới
            Point scaledOriginalPinchCenter = new Point(
                originalPinchCenter.X * newScale,
                originalPinchCenter.Y * newScale);

            // Độ dịch tâm khi áp dụng scale mới
            Point transitionDelta = new Point(
                scaledOriginalPinchCenter.X - _pinchCenter.X,
                scaledOriginalPinchCenter.Y - _pinchCenter.Y);

            // Đổi sang vị trí tâm sau khi scale.
            _pinchCenter = scaledOriginalPinchCenter;

            // Chỉnh lại theo tọa độ của root. 
            return new Point(-transitionDelta.X / _imageSizeRate.X, -transitionDelta.Y / _imageSizeRate.Y);
        }

        /// <summary>
        /// Tính lượng dịch tâm sau khi scale
        /// </summary>
        /// <param name="pinchCenter">Tâm để tính</param>
        /// <param name="newScale">Lượng scale được áp dụng</param>
        /// <returns></returns>
        private Point CalculateTransition(Point pinchCenter, double newScale)
        {
            return new Point();
        }

        /// <summary>
        /// Lấy giá trị scale thích hợp bằng cách so sánh với _minScale và _maxScale.
        /// </summary>
        /// <param name="scale">giá trị scale được kiểm tra</param>
        /// <returns>giá trị scale thích hợp</returns>
        private double GetValidScale(double scale)
        {
            if (scale < _minScale)
            {
                return _minScale;
                //return _preferredMinScale;
            }
            else if (scale > _maxScale)
            {
                return _maxScale;
            }

            return scale;
        }

        /// <summary>
        /// Lấy vị trí của ảnh (tính theo viewbox) và kích thước của ảnh (tính theo root)
        /// </summary>
        /// <returns>Vị trí và kích thước ảnh</returns>
        private Rect GetImagePosition()
        {
            return new Rect(
                transform.TranslateX / _imageSizeRate.X,
                transform.TranslateY / _imageSizeRate.Y,
                _imageSize.Width * transform.ScaleX,
                _imageSize.Height * transform.ScaleY);
        }

        /// <summary>
        /// Lấy vị trí của ảnh (tính theo viewbox) và kích thước của ảnh (tính theo root)
        /// </summary>
        /// <param name="delta">Tổng lượng dịch chuyển</param>
        /// <param name="scale">Tổng lượng scale</param>
        /// <returns></returns>
        private Rect GetImagePosition(Point delta, double scale)
        {
            return new Rect(
                delta.X / _imageSizeRate.X,
                delta.Y / _imageSizeRate.Y,
                _imageSize.Width * scale,
                _imageSize.Height * scale);
        }

        /// <summary>
        /// Chuyển tọa độ của image từ viewbox sang root. Tọa độ root giống viewbox nếu viewbox > root.
        /// </summary>
        /// <returns></returns>
        private Rect GetImageRootPosition()
        {
            Rect imageRootPosition = GetImagePosition();

            if (viewbox.ActualWidth < root.ActualWidth)
            {
                imageRootPosition.X = (root.ActualWidth - viewbox.ActualWidth) / 2 + imageRootPosition.Left;
            }
            if (viewbox.ActualHeight < root.ActualHeight)
            {
                imageRootPosition.Y = (root.ActualHeight - viewbox.ActualHeight) / 2 + imageRootPosition.Top;
            }

            return imageRootPosition;
        }
        #endregion

        #region //--------- Helpers -----------//
        /// <summary>
        /// Thiết lập các biến cần thiết theo chiều dài và rộng của các đối tượng.
        /// </summary>
        private void InitializeVariables()
        {
            // Khoảng cách dùng để tính biên
            double distance;

            if (viewbox.ActualHeight < root.ActualHeight || viewbox.ActualWidth < root.ActualWidth)
            {
                distance = Math.Min(viewbox.ActualWidth * _boundRate, viewbox.ActualHeight * _boundRate);
            }
            else
            {
                distance = Math.Min(root.ActualWidth * _boundRate, root.ActualHeight * _boundRate);
            }

            // Biên mặc định, tính theo root, sẽ được căn chỉnh nếu viewbox < root
            _innerBound = new Rect(
                distance,
                distance,
                root.ActualWidth - 2 * distance,
                root.ActualHeight - 2 * distance);

            if (viewbox.ActualWidth < root.ActualWidth)
            {
                double diff = root.ActualWidth - viewbox.ActualWidth;
                _innerBound.Width = root.ActualWidth - diff - 2 * distance;
            }

            if (viewbox.ActualHeight < root.ActualHeight)
            {
                double diff = root.ActualHeight - viewbox.ActualHeight;
                _innerBound.Height = root.ActualHeight - diff - 2 * distance;
            }

            // Kích thước thật của ảnh
            _imageSize = new Size(
                viewbox.ActualWidth,
                viewbox.ActualHeight);

            // Tỉ lệ kích thước của ảnh so với viewbox
            _imageSizeRate = new Point(
                image.ActualWidth / viewbox.ActualWidth,
                image.ActualHeight / viewbox.ActualHeight);

            // Độ scale của image tính theo chiều dài và rộng của viewbox
            // Lấy giá trị lớn hơn vì lấy giá trị nhỏ hơn thì 1 kích thước sẽ nhỏ hơn _innerBound
            _minScale = Math.Max(
                _innerBound.Width / _imageSize.Width,
                _innerBound.Height / _imageSize.Height);

            // Các giá trị mặc định cho các biến scale
            _maxScale = 8.0 * _minScale;
            _preferredMaxScale = 0.6 * _maxScale;
            _preferredMinScale = Math.Max(
                viewbox.ActualWidth / _imageSize.Width,
                viewbox.ActualHeight / _imageSize.Height);
        }

        /// <summary>
        /// Đặt biên độ cho các hàm easing
        /// </summary>
        /// <param name="amplitude"></param>
        private void SetBackEaseAmplitude(double amplitude)
        {
            restoreScaleXBackEase.Amplitude = restoreScaleYBackEase.Amplitude = amplitude;
            restoreTranslateXBackEase.Amplitude = restoreTranslateYBackEase.Amplitude = amplitude;
        }
        #endregion 
        #endregion

        #region //----------------------------- Overridden Methods ----------------------------------------//
        protected override void OnManipulationStarted(ManipulationStartedEventArgs args)
        {
            if (_flickStoryboardState == StoryboardState.Running)
            {
                flickStoryboard.Pause();
                _flickStoryboardState = StoryboardState.Paused;
            }

            args.Handled = true;
            args.Complete();

            base.OnManipulationStarted(args);
        }
        #endregion
    }
}